package org.japo.java.binding.samples;

import java.awt.Color;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.net.ServerSocket;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import org.apache.commons.beanutils.BeanUtils;
import org.japo.java.binding.converter.FechaConverter;
import org.japo.java.binding.converter.HoraConverter;
import org.japo.java.binding.converter.NifConverter;
import org.japo.java.entities.Cita;
import org.japo.java.entities.Fecha;
import org.japo.java.entities.Hora;
import org.japo.java.entities.Nif;

/**
 *
 * @author José A. Pacheco Ondoño - joanpaon@gmail.com
 */
public class CitaITV extends javax.swing.JFrame {

    // Referencia al modelo
    private Cita cita;

    // Fichero de persistencia
    private final String FICHERO_XML = "cita.xml";

    // Ficheros auxiliares de la aplicacion
    private final String FICHERO_INI = "config.ini";

    // Propiedades de la aplicacion
    private final Properties propiedades = new Properties();

    // Referencia al ServerSocket de ejecución única
    private ServerSocket ss = null;

    // Semáforos de validación
    private boolean nifOK;
    private boolean nombreOK;
    private boolean apellidosOK;
    private boolean matriculaOK;
    private boolean fechaOK;
    private boolean horaOK;

    // Expresiones regulares
    private final String ER_NIF = "[0-9]{8}[TRWAGMYFPDXBNJZSQVHLCKE]"; // NIF
    private final String ER_NOMBRE = ".{3,30}";           // Nombre
    private final String ER_APELLIDOS = ".{3,30}";        // Apellidos
    private final String ER_MATRICULA = "([A-Z]{1,2}-[0-9]{1,5})|([A-Z]{1,2}-[0-9]{4}-[A-Z]{1,2})|[A-Z]{3}-[0-9]{4}";     // Matricula
    private final String ER_FECHA = "(0[1-9]|[12][0-9]|3[01])/(0[13578]|1[02])/[0-9]+|(0[1-9]|[12][0-9]|30)/(0[469]|11)/[0-9]+|(0[1-9]|1[0-9]|2[0-9])/02/[0-9]+";
    private final String ER_HORA = "([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]";

    public CitaITV() {
        iniciarAnterior();
        initComponents();
        iniciarPosterior();
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {
        bindingGroup = new org.jdesktop.beansbinding.BindingGroup();

        pnlCliente = new javax.swing.JPanel();
        lblNif = new javax.swing.JLabel();
        txfNif = new javax.swing.JTextField();
        lblNombre = new javax.swing.JLabel();
        txfNombre = new javax.swing.JTextField();
        lblApellidos = new javax.swing.JLabel();
        txfApellidos = new javax.swing.JTextField();
        pnlVehiculo = new javax.swing.JPanel();
        lblMatricula = new javax.swing.JLabel();
        txfMatricula = new javax.swing.JTextField();
        lblTipo = new javax.swing.JLabel();
        cbbTipo = new javax.swing.JComboBox();
        pnlCita = new javax.swing.JPanel();
        lblFecha = new javax.swing.JLabel();
        txfFecha = new javax.swing.JTextField();
        lblHora = new javax.swing.JLabel();
        txfHora = new javax.swing.JTextField();
        pnlOperaciones = new javax.swing.JPanel();
        btnCargar = new javax.swing.JButton();
        btnGuardar = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("Cita previa ITV");
        setResizable(false);
        addWindowListener(new java.awt.event.WindowAdapter() {
            public void windowClosing(java.awt.event.WindowEvent evt) {
                formWindowClosing(evt);
            }
        });

        pnlCliente.setBorder(javax.swing.BorderFactory.createTitledBorder(null, "Clente", javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION, javax.swing.border.TitledBorder.DEFAULT_POSITION, new java.awt.Font("SansSerif", 0, 12))); // NOI18N

        lblNif.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N
        lblNif.setText("NIF");

        txfNif.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N

        org.jdesktop.beansbinding.Binding binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, this, org.jdesktop.beansbinding.ELProperty.create("${cita.nif}"), txfNif, org.jdesktop.beansbinding.BeanProperty.create("text"), "nif");
        binding.setConverter(new NifConverter());
        bindingGroup.addBinding(binding);

        txfNif.addFocusListener(new java.awt.event.FocusAdapter() {
            public void focusGained(java.awt.event.FocusEvent evt) {
                txfNifFocusGained(evt);
            }
            public void focusLost(java.awt.event.FocusEvent evt) {
                txfNifFocusLost(evt);
            }
        });

        lblNombre.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N
        lblNombre.setText("Nombre");

        txfNombre.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N

        binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, this, org.jdesktop.beansbinding.ELProperty.create("${cita.nombre}"), txfNombre, org.jdesktop.beansbinding.BeanProperty.create("text"), "nombre");
        bindingGroup.addBinding(binding);

        txfNombre.addFocusListener(new java.awt.event.FocusAdapter() {
            public void focusGained(java.awt.event.FocusEvent evt) {
                txfNombreFocusGained(evt);
            }
            public void focusLost(java.awt.event.FocusEvent evt) {
                txfNombreFocusLost(evt);
            }
        });

        lblApellidos.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N
        lblApellidos.setText("Apellidos");

        txfApellidos.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N

        binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, this, org.jdesktop.beansbinding.ELProperty.create("${cita.apellidos}"), txfApellidos, org.jdesktop.beansbinding.BeanProperty.create("text"), "apellidos");
        bindingGroup.addBinding(binding);

        txfApellidos.addFocusListener(new java.awt.event.FocusAdapter() {
            public void focusGained(java.awt.event.FocusEvent evt) {
                txfApellidosFocusGained(evt);
            }
            public void focusLost(java.awt.event.FocusEvent evt) {
                txfApellidosFocusLost(evt);
            }
        });

        javax.swing.GroupLayout pnlClienteLayout = new javax.swing.GroupLayout(pnlCliente);
        pnlCliente.setLayout(pnlClienteLayout);
        pnlClienteLayout.setHorizontalGroup(
            pnlClienteLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(pnlClienteLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(pnlClienteLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(pnlClienteLayout.createSequentialGroup()
                        .addComponent(lblNombre)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(txfNombre, javax.swing.GroupLayout.PREFERRED_SIZE, 246, javax.swing.GroupLayout.PREFERRED_SIZE))
                    .addGroup(pnlClienteLayout.createSequentialGroup()
                        .addComponent(lblNif)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(txfNif, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                    .addGroup(pnlClienteLayout.createSequentialGroup()
                        .addComponent(lblApellidos)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(txfApellidos, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        pnlClienteLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {lblApellidos, lblNif, lblNombre});

        pnlClienteLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {txfApellidos, txfNif, txfNombre});

        pnlClienteLayout.setVerticalGroup(
            pnlClienteLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(pnlClienteLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(pnlClienteLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(lblNif)
                    .addComponent(txfNif, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addGroup(pnlClienteLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(lblNombre)
                    .addComponent(txfNombre, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addGroup(pnlClienteLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(lblApellidos)
                    .addComponent(txfApellidos, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        pnlClienteLayout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {lblApellidos, lblNif, lblNombre, txfApellidos, txfNif, txfNombre});

        pnlVehiculo.setBorder(javax.swing.BorderFactory.createTitledBorder(null, "Vehículo", javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION, javax.swing.border.TitledBorder.DEFAULT_POSITION, new java.awt.Font("SansSerif", 0, 12))); // NOI18N

        lblMatricula.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N
        lblMatricula.setText("Matrícula");

        txfMatricula.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N

        binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, this, org.jdesktop.beansbinding.ELProperty.create("${cita.matricula}"), txfMatricula, org.jdesktop.beansbinding.BeanProperty.create("text"), "matricula");
        bindingGroup.addBinding(binding);

        txfMatricula.addFocusListener(new java.awt.event.FocusAdapter() {
            public void focusGained(java.awt.event.FocusEvent evt) {
                txfMatriculaFocusGained(evt);
            }
            public void focusLost(java.awt.event.FocusEvent evt) {
                txfMatriculaFocusLost(evt);
            }
        });

        lblTipo.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N
        lblTipo.setText("Tipo");

        cbbTipo.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N
        cbbTipo.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Motocicleta", "Turismo", "Camión" }));

        binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, this, org.jdesktop.beansbinding.ELProperty.create("${cita.tipo}"), cbbTipo, org.jdesktop.beansbinding.BeanProperty.create("selectedItem"), "tipo");
        bindingGroup.addBinding(binding);

        javax.swing.GroupLayout pnlVehiculoLayout = new javax.swing.GroupLayout(pnlVehiculo);
        pnlVehiculo.setLayout(pnlVehiculoLayout);
        pnlVehiculoLayout.setHorizontalGroup(
            pnlVehiculoLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(pnlVehiculoLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(pnlVehiculoLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                    .addComponent(lblMatricula, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addComponent(lblTipo, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(pnlVehiculoLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(txfMatricula)
                    .addComponent(cbbTipo, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addContainerGap())
        );
        pnlVehiculoLayout.setVerticalGroup(
            pnlVehiculoLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(pnlVehiculoLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(pnlVehiculoLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(lblMatricula)
                    .addComponent(txfMatricula, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addGap(18, 18, 18)
                .addGroup(pnlVehiculoLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(lblTipo)
                    .addComponent(cbbTipo, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        pnlVehiculoLayout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {cbbTipo, lblMatricula, lblTipo, txfMatricula});

        pnlCita.setBorder(javax.swing.BorderFactory.createTitledBorder(null, "Cita", javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION, javax.swing.border.TitledBorder.DEFAULT_POSITION, new java.awt.Font("SansSerif", 0, 12))); // NOI18N

        lblFecha.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N
        lblFecha.setText("Fecha");

        txfFecha.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N

        binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, this, org.jdesktop.beansbinding.ELProperty.create("${cita.fecha}"), txfFecha, org.jdesktop.beansbinding.BeanProperty.create("text"), "fecha");
        binding.setConverter(new FechaConverter());
        bindingGroup.addBinding(binding);

        txfFecha.addFocusListener(new java.awt.event.FocusAdapter() {
            public void focusGained(java.awt.event.FocusEvent evt) {
                txfFechaFocusGained(evt);
            }
            public void focusLost(java.awt.event.FocusEvent evt) {
                txfFechaFocusLost(evt);
            }
        });

        lblHora.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N
        lblHora.setText("Hora");

        txfHora.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N

        binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, this, org.jdesktop.beansbinding.ELProperty.create("${cita.hora}"), txfHora, org.jdesktop.beansbinding.BeanProperty.create("text"), "hora");
        binding.setConverter(new HoraConverter());
        bindingGroup.addBinding(binding);

        txfHora.addFocusListener(new java.awt.event.FocusAdapter() {
            public void focusGained(java.awt.event.FocusEvent evt) {
                txfHoraFocusGained(evt);
            }
            public void focusLost(java.awt.event.FocusEvent evt) {
                txfHoraFocusLost(evt);
            }
        });

        javax.swing.GroupLayout pnlCitaLayout = new javax.swing.GroupLayout(pnlCita);
        pnlCita.setLayout(pnlCitaLayout);
        pnlCitaLayout.setHorizontalGroup(
            pnlCitaLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(pnlCitaLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(pnlCitaLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(pnlCitaLayout.createSequentialGroup()
                        .addComponent(lblFecha)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                        .addComponent(txfFecha, javax.swing.GroupLayout.PREFERRED_SIZE, 247, javax.swing.GroupLayout.PREFERRED_SIZE))
                    .addGroup(pnlCitaLayout.createSequentialGroup()
                        .addComponent(lblHora)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                        .addComponent(txfHora, javax.swing.GroupLayout.PREFERRED_SIZE, 247, javax.swing.GroupLayout.PREFERRED_SIZE)))
                .addContainerGap())
        );
        pnlCitaLayout.setVerticalGroup(
            pnlCitaLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(pnlCitaLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(pnlCitaLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(lblFecha)
                    .addComponent(txfFecha, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addGap(18, 18, 18)
                .addGroup(pnlCitaLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(lblHora)
                    .addComponent(txfHora, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        pnlCitaLayout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {lblFecha, lblHora, txfFecha, txfHora});

        pnlOperaciones.setBorder(javax.swing.BorderFactory.createTitledBorder(null, "Operaciones", javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION, javax.swing.border.TitledBorder.DEFAULT_POSITION, new java.awt.Font("SansSerif", 0, 12))); // NOI18N

        btnCargar.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N
        btnCargar.setText("Cargar");
        btnCargar.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnCargarActionPerformed(evt);
            }
        });

        btnGuardar.setFont(new java.awt.Font("SansSerif", 0, 18)); // NOI18N
        btnGuardar.setText("Guardar");
        btnGuardar.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnGuardarActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout pnlOperacionesLayout = new javax.swing.GroupLayout(pnlOperaciones);
        pnlOperaciones.setLayout(pnlOperacionesLayout);
        pnlOperacionesLayout.setHorizontalGroup(
            pnlOperacionesLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(pnlOperacionesLayout.createSequentialGroup()
                .addContainerGap()
                .addComponent(btnCargar)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addComponent(btnGuardar, javax.swing.GroupLayout.PREFERRED_SIZE, 143, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap())
        );

        pnlOperacionesLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {btnCargar, btnGuardar});

        pnlOperacionesLayout.setVerticalGroup(
            pnlOperacionesLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(pnlOperacionesLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(pnlOperacionesLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(btnCargar)
                    .addComponent(btnGuardar))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                    .addComponent(pnlVehiculo, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addComponent(pnlCliente, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addComponent(pnlCita, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addComponent(pnlOperaciones, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(pnlCliente, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(pnlVehiculo, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(pnlCita, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(pnlOperaciones, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        bindingGroup.bind();

        pack();
        setLocationRelativeTo(null);
    }// </editor-fold>//GEN-END:initComponents

    private void txfNifFocusGained(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txfNifFocusGained
        // Color de la letra en modo edición
        txfNif.setForeground(Color.BLACK);

        // Color de fondo de campo seleccionado
        txfNif.setBackground(Color.CYAN);
    }//GEN-LAST:event_txfNifFocusGained

    private void txfNombreFocusGained(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txfNombreFocusGained
        // Color de la letra en modo edición
        txfNombre.setForeground(Color.BLACK);

        // Color de fondo de campo seleccionado
        txfNombre.setBackground(Color.CYAN);
    }//GEN-LAST:event_txfNombreFocusGained

    private void txfApellidosFocusGained(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txfApellidosFocusGained
        // Color de la letra en modo edición
        txfApellidos.setForeground(Color.BLACK);

        // Color de fondo de campo seleccionado
        txfApellidos.setBackground(Color.CYAN);
    }//GEN-LAST:event_txfApellidosFocusGained

    private void txfMatriculaFocusGained(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txfMatriculaFocusGained
        // Color de la letra en modo edición
        txfMatricula.setForeground(Color.BLACK);

        // Color de fondo de campo seleccionado
        txfMatricula.setBackground(Color.CYAN);
    }//GEN-LAST:event_txfMatriculaFocusGained

    private void txfFechaFocusGained(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txfFechaFocusGained
        // Color de la letra en modo edición
        txfFecha.setForeground(Color.BLACK);

        // Color de fondo de campo seleccionado
        txfFecha.setBackground(Color.CYAN);
    }//GEN-LAST:event_txfFechaFocusGained

    private void txfHoraFocusGained(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txfHoraFocusGained
        // Color de la letra en modo edición
        txfHora.setForeground(Color.BLACK);

        // Color de fondo de campo seleccionado
        txfHora.setBackground(Color.CYAN);
    }//GEN-LAST:event_txfHoraFocusGained

    private void txfNifFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txfNifFocusLost
        // Valida el campo
        nifOK = validarCampoTexto(txfNif, ER_NIF, "*");

        // Valida el Nif
        try {
            Nif nif = Nif.parseNif(txfNif.getText());
            txfNif.setText(nif.toString());
            txfNif.setForeground(Color.BLACK);
            nifOK = true;
        } catch (Exception e) {
            txfNif.setForeground(Color.RED);
            nifOK = true;
        }

        // Color de fondo de campo seleccionado
        txfNif.setBackground(Color.WHITE);
    }//GEN-LAST:event_txfNifFocusLost

    private void txfNombreFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txfNombreFocusLost
        // Valida el campo
        nombreOK = validarCampoTexto(txfNombre, ER_NOMBRE, "*");

        // Color de fondo de campo seleccionado
        txfNombre.setBackground(Color.WHITE);
    }//GEN-LAST:event_txfNombreFocusLost

    private void txfApellidosFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txfApellidosFocusLost
        // Valida el campo
        apellidosOK = validarCampoTexto(txfApellidos, ER_APELLIDOS, "*");

        // Color de fondo de campo seleccionado
        txfApellidos.setBackground(Color.WHITE);
    }//GEN-LAST:event_txfApellidosFocusLost

    private void txfMatriculaFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txfMatriculaFocusLost
        // Valida el campo
        matriculaOK = validarCampoTexto(txfMatricula, ER_MATRICULA, "*");

        // Color de fondo de campo seleccionado
        txfMatricula.setBackground(Color.WHITE);
    }//GEN-LAST:event_txfMatriculaFocusLost

    private void txfFechaFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txfFechaFocusLost
        // Valida el campo
        fechaOK = validarCampoTexto(txfFecha, ER_FECHA, "*");

        // Valida la fecha
        try {
            Fecha fecha = Fecha.parseFecha(txfFecha.getText());
            txfFecha.setText(fecha.toString());
            txfFecha.setForeground(Color.BLACK);
            fechaOK = true;
        } catch (NumberFormatException e) {
            txfFecha.setForeground(Color.RED);
            fechaOK = true;
        }

        // Color de fondo de campo seleccionado
        txfFecha.setBackground(Color.WHITE);
    }//GEN-LAST:event_txfFechaFocusLost

    private void txfHoraFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txfHoraFocusLost
        // Valida el campo
        horaOK = validarCampoTexto(txfHora, ER_HORA, "*");

        // Valida la hora
        try {
            Hora hora = Hora.parseHora(txfHora.getText());
            txfHora.setText(hora.toString());
            txfHora.setForeground(Color.BLACK);
            horaOK = true;
        } catch (NumberFormatException e) {
            txfHora.setForeground(Color.RED);
            horaOK = true;
        }

        // Color de fondo de campo seleccionado
        txfHora.setBackground(Color.WHITE);
    }//GEN-LAST:event_txfHoraFocusLost

    private void btnCargarActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnCargarActionPerformed
        try {
            // Matricula a partir de los datos del fichero
            importarModelo();

            // Comprobar validez de los datos cargados en el interfaz
            comprobarValidez();
        } catch (Exception ex) {
            JOptionPane.showMessageDialog(this, "Error al cargar los datos");
        }
    }//GEN-LAST:event_btnCargarActionPerformed

    private void btnGuardarActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnGuardarActionPerformed
        try {
            if (comprobarValidez()) {
                // Guarda los datos en fichero
                exportarModelo();
            } else {
                JOptionPane.showMessageDialog(this, "Hay datos incorrectos.");
            }
        } catch (Exception ex) {
            JOptionPane.showMessageDialog(this, "Error al guardar los datos.");
        }
    }//GEN-LAST:event_btnGuardarActionPerformed

    private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing
        try {
            // Guarda las propiedades 
            guardarPropiedades();
        } catch (Exception e) {
            // Muestra la excepción encontrada
            JOptionPane.showMessageDialog(this, e.toString());

            // Termina el programa
            terminarPrograma();
        }
    }//GEN-LAST:event_formWindowClosing

    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        /* Set the Nimbus look and feel */
        //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
        /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
         * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html 
         */
        try {
            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
                if ("Windows".equals(info.getName())) {
                    javax.swing.UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (ClassNotFoundException ex) {
            java.util.logging.Logger.getLogger(CitaITV.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (InstantiationException ex) {
            java.util.logging.Logger.getLogger(CitaITV.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            java.util.logging.Logger.getLogger(CitaITV.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (javax.swing.UnsupportedLookAndFeelException ex) {
            java.util.logging.Logger.getLogger(CitaITV.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        //</editor-fold>

        /* Create and display the form */
        java.awt.EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new CitaITV().setVisible(true);
            }
        });
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton btnCargar;
    private javax.swing.JButton btnGuardar;
    private javax.swing.JComboBox cbbTipo;
    private javax.swing.JLabel lblApellidos;
    private javax.swing.JLabel lblFecha;
    private javax.swing.JLabel lblHora;
    private javax.swing.JLabel lblMatricula;
    private javax.swing.JLabel lblNif;
    private javax.swing.JLabel lblNombre;
    private javax.swing.JLabel lblTipo;
    private javax.swing.JPanel pnlCita;
    private javax.swing.JPanel pnlCliente;
    private javax.swing.JPanel pnlOperaciones;
    private javax.swing.JPanel pnlVehiculo;
    private javax.swing.JTextField txfApellidos;
    private javax.swing.JTextField txfFecha;
    private javax.swing.JTextField txfHora;
    private javax.swing.JTextField txfMatricula;
    private javax.swing.JTextField txfNif;
    private javax.swing.JTextField txfNombre;
    private org.jdesktop.beansbinding.BindingGroup bindingGroup;
    // End of variables declaration//GEN-END:variables

    private void exportarModelo() throws Exception {
        // Codificador XML de objetos
        XMLEncoder encoder = null;

        // Cauce de salida al fichero XML
        FileOutputStream fos = null;

        try {
            // Cauce binario de salida a fichero
            fos = new FileOutputStream(FICHERO_XML);

            // Codificador de serialización XML
            encoder = new XMLEncoder(fos);

            // Guarda el objeto
            encoder.writeObject(cita);
        } finally {
            // Cierra el codificador XML
            if (encoder != null) {
                encoder.close();
            }

            // Cierra el acceso de salida al fichero
            if (fos != null) {
                fos.close();
            }
        }
    }

    private void importarModelo() throws Exception {
        // Decodificador XML
        XMLDecoder decoder = null;

        // Cauce de entrada al fichero XML        
        FileInputStream fis = null;

        try {
            // Cauce binario de lectura de un fichero
            fis = new FileInputStream(FICHERO_XML);

            // Decodificador
            decoder = new XMLDecoder(fis);

            // Objeto temporal con los datos leidos
            Cita citaAct = (Cita) decoder.readObject();

            // Copia masiva de atributos
            BeanUtils.copyProperties(cita, citaAct);
        } finally {
            // Cierra el decodificador XML
            if (decoder != null) {
                decoder.close();
            }

            // Cierra el acceso de entrada al fichero
            if (fis != null) {
                fis.close();
            }
        }
    }

    private boolean validarCampoTexto(JTextField txfActual, String expReg,
            String textoCampoVacio) throws PatternSyntaxException {
        // Semáforo de validación
        boolean validacionOK = false;

        try {
            // Obtiene el texto del campo
            String textoActual = txfActual.getText().trim();

            // Comprueba campo vacio
            if (textoActual.equals("")) {
                textoActual = textoCampoVacio;
            }

            // Restaura el texto formateado
            txfActual.setText(textoActual);

            // Patrón de la expresión regular
            Pattern patron = Pattern.compile(expReg);

            // Aplica el patrón al texto de prueba
            Matcher detector = patron.matcher(textoActual);

            // Comprueba la coincidencia
            if (detector.matches()) {
                // Contenido correcto
                txfActual.setForeground(Color.BLACK);

                // Actualiza el semáforo
                validacionOK = true;
            } else {
                // Contenido problemático
                txfActual.setForeground(Color.RED);
            }
        } catch (PatternSyntaxException pse) {
            // Contenido problemático
            txfActual.setForeground(Color.RED);

            // Expresión regular errónea - Depurar expresión
            JOptionPane.showMessageDialog(this, "Expresión regular errónea");
        }

        // Devuelve el resultado de la validacion
        return validacionOK;
    }

    private boolean comprobarValidez() {
        // NIF
        nifOK = validarCampoTexto(txfNif, ER_NIF, "*");

        // Valida el Nif
        try {
            Nif nif = Nif.parseNif(txfNif.getText());
            txfNif.setText(nif.toString());
            txfNif.setForeground(Color.BLACK);
            nifOK = true;
        } catch (Exception e) {
            txfNif.setForeground(Color.RED);
            nifOK = true;
        }

        // Nombre
        nombreOK = validarCampoTexto(txfNombre, ER_NOMBRE, "*");

        // Apellidos
        apellidosOK = validarCampoTexto(txfApellidos, ER_APELLIDOS, "*");

        // Matricula
        matriculaOK = validarCampoTexto(txfMatricula, ER_MATRICULA, "*");

        // No hay una ER sencilla que permita validar completamente una fecha
        fechaOK = validarCampoTexto(txfFecha, ER_FECHA, "*");

        // Valida la fecha
        try {
            Fecha fecha = Fecha.parseFecha(txfFecha.getText());
            txfFecha.setText(fecha.toString());
            txfFecha.setForeground(Color.BLACK);
            fechaOK = true;
        } catch (NumberFormatException e) {
            txfFecha.setForeground(Color.RED);
            fechaOK = true;
        }

        // Hota
        horaOK = validarCampoTexto(txfHora, ER_HORA, "*");

        // Valida la hora
        try {
            Hora hora = Hora.parseHora(txfHora.getText());
            txfHora.setText(hora.toString());
            txfHora.setForeground(Color.BLACK);
            horaOK = true;
        } catch (NumberFormatException e) {
            txfHora.setForeground(Color.RED);
            horaOK = true;
        }

        // Genera el semaforo general de validación
        return nifOK && nombreOK && apellidosOK && matriculaOK && fechaOK && horaOK;
    }

    public Cita getCita() {
        return cita;
    }

    public void setCita(Cita cita) {
        this.cita = cita;
    }

    private void iniciarAnterior() {
        // Carga las propiedades
        cargarPropiedades();

        // Ejecución unica
        validarInstanciaUnica();

        // Instancia el modelo
        cita = new Cita();
    }

    private void iniciarPosterior() {
        // Se aplican las propedades guardadas
        restaurarEstadoVentana();
    }

    private void cargarPropiedades() {
        try (FileReader fr = new FileReader(FICHERO_INI)) {
            // Carga las propiedades desde el fichero
            propiedades.load(fr);
        } catch (Exception e) {
            System.out.println(e);
        }
    }

    private void validarInstanciaUnica() {
        try {
            activarInstancia();
        } catch (Exception ex) {
            terminarPrograma();
        }
    }

    private void activarInstancia() throws Exception {
        // Fichero de bloqueo predeterminado
        final int PUERTO_LCK = 54321;

        // Puerto de bloqueo
        int puertoBloqueo = obtenerPropiedadEntero("bloqueo", PUERTO_LCK);

        // Abre un ServerSocket al puerto de bloqueo
        ss = new ServerSocket(puertoBloqueo);
    }

    private int obtenerPropiedadEntero(String propiedad, int predeterminado) {
        // Valor a devolver
        int i = predeterminado;

        try {
            // Obtiene la propiedad actual
            String valor = propiedades.getProperty(propiedad);

            // Analiza si la propiedad es nula
            if (valor != null) {
                // Genera un nuevo Integer
                i = Integer.parseInt(valor);
            } else {
                // Establece la propiedad con el valor predeterminado
                propiedades.setProperty(propiedad, predeterminado + "");
            }
        } catch (NumberFormatException e) {
            // Muestra la excepcion
            System.out.println(e);

            // Establece la propiedad con el valor predeterminado
            propiedades.setProperty(propiedad, predeterminado + "");
        }

        // Devuelve el valor
        return i;
    }

    private void terminarPrograma() {
        setVisible(false);
        dispose();
        System.exit(0);
    }

    private void guardarPropiedades() {
        // Proceso de salvaguarda de propiedades
        try (FileWriter fw = new FileWriter(FICHERO_INI)) {
            // Actualizar propiedades
            actualizarPropiedades();

            // Carga las propiedades
            propiedades.store(fw, null);
        } catch (Exception e) {
            System.out.println(e);
        }
    }

    private void actualizarPropiedades() {
        // Posicion X de la ventana
        int x = this.getLocation().x;
        propiedades.setProperty("x", x + "");

        // Posicion Y de la ventana
        int y = this.getLocation().y;
        propiedades.setProperty("y", y + "");

        // Estado de la ventana
        int estado = this.getExtendedState();
        propiedades.setProperty("estado", estado + "");
    }

    private void restaurarEstadoVentana() {
        // Posicion X de la ventana
        int x = this.getLocation().x;
        x = obtenerPropiedadEntero("x", x);

        // Posicion Y de la ventana
        int y = this.getLocation().y;
        y = obtenerPropiedadEntero("y", y);

        // Estado de la ventana
        int estado = this.getExtendedState();
        estado = obtenerPropiedadEntero("estado", estado);

        // Aplicar propiedades
        this.setLocation(x, y);
        this.setExtendedState(estado);
    }
}
